#####################################################################
# Product:      KnowledgeEngine (Plone Edition)
#
# File:         PropertyDefinition.py
#
# Copyright:    Copyright (c) 2004, Faulkner Technologies
#
# Author:       Brian Oliver
#
# Description:  A PropertyDefinition is an object that represents
#               runtime type information pertaining to a persistent
#               property of a GenericObject
#
# See Also:     PropertyAccess, PropertyReflection
#####################################################################

#####################################################################
## KnowledgeEngine Library Imports
from Common import *
from PropertyTypes import *
from PropertyStructures import *
from PropertyFormats import *
from Editlet import *
from Core import getEditletFor

#####################################################################
## Zope Library Imports
from Globals import InitializeClass
from AccessControl import ClassSecurityInfo
from AccessControl.Role import RoleManager
 
#####################################################################
## Python Library Imports
# (none)

#Editlet Value Access Permissions (used to define how individual editors for properties may be rendered)
evap_NONE = 0          #no access permitted (not displayed)
evap_READONLY = 1      #value displayed but not changeable
evap_WRITEONLY = 2     #value may be written but current value not displayed 
evap_READWRITE = 3     #value may be displayed and changed

#####################################################################
## PropertyDefinition Class Definition
class PropertyDefinition(RoleManager):
    """
        Instances of this class define individual property information
        for properties of GenericObjects
    """

    #---------------------------------------------------------------#

    #define security
    security = ClassSecurityInfo()
    security.declareObjectPublic()
    security.setDefaultAccess("allow")

    #---------------------------------------------------------------#

    def __init__(self, 
                 identity, 
                 displayName, 
                 description="", 
                 displayOrder=0, 
                 defaultValue=None, 
                 propertyType=pt_STRING, 
                 propertyStructure=ps_ATOMIC, 
                 propertyFormat=pf_NATIVE, 
                 hasEnumeratedValues=false, 
                 enumeratedValuesFunction=None, 
                 enumeratedValuesMapping=None, 
                 enumeratedValuesList=None, 
                 constructorEVAP=evap_READWRITE,
                 propertyEditorEVAP=evap_READWRITE,
                 isMandatory=false, 
                 **args):
        """
            Stores the state of a Property Definition.
            
            PARAMETERS:
    
            displayOrder:               Specifies the relative position of the property to other
                                        registered properties for the class.  Specifying a value of zero (0)
                                        will make the property appear at the end of the list of
                                        properties for a class.  Default is 0.
                                        
            defaultValue:               Specifies the default value when constructing a new object.
                                        Default is None.
            
            hasEnumeratedValues:        Specifies if the property uses enumerated (selectable) values.
                                        To provide actual values, additionally specify either the
                                        enumeratedValuesFunction, enumeratedValuesMapping
                                        or enumeratedValueList parameters. Default is false.
                                        
            enumeratedValuesFunction:   A function of the type 'func(context) -> [(DisplayName,Value)]' 
                                        that produces a list of tuples containing the valid
                                        values for an enumerated type.  Default is None.

            enumeratedValuesMapping:    A list of '[(DisplayName,Value)]' mapping display names 
                                        to values for an enumerated type.  Default is None.
                                        
            enumeratedValuesList:       A list of '[Value]' values (also used as display names)
                                        as values for an enumerated type.  Default is None.
            
            constructorEVAP:            Specifies the editlet value access permission in the constructor property editor 
                                        for this property. Default is evap_READWRITE.
                                        
                                        This attribute permits control over the display and editing of the property value in
                                        a constructor property editor.
            
            propertyEditorEVAP:         Specifies the editlet value access permission in the standard property editor 
                                        for this property. Default is evap_READWRITE.
                                        
                                        This attribute permits control over the display and editing of the property value in
                                        a property editor.
                                        
            isMandatory:                Specifies if the property must be provided (is not allowed to be empty)
                                        when edited using a property editor.  If a property is not set (or is None) when 
                                        it isMandatory, the appropriate editlet must (if possible) pretend it is in write
                                        mode for the editlet to ensure that a value is provided.  The editlet may only pretend
                                        it's in write mode if either the constructorEVAP or propertyEditorEVAP allows writing.
                                        
            **args:                     Any additional NAMED argurments will be collected and made available by
                                        the getArg(name), setArg(name, value) and hasArg(name) methods on this class.
        """        
        
        self.identity = identity
        self.displayName = displayName
        self.description = description
        self.displayOrder = displayOrder
        self.defaultValue = defaultValue
        self.propertyType = propertyType
        self.propertyStructure = propertyStructure
        self.propertyFormat = propertyFormat
        self.isEnumeratedProperty = hasEnumeratedValues
        self.enumeratedValuesFunction = enumeratedValuesFunction
        self.enumeratedValuesMapping = enumeratedValuesMapping
        self.enumeratedValuesList = enumeratedValuesList
        
        #editlet value access permissions for constructor and regular property editors
        self.constructorEVAP = constructorEVAP
        self.propertyEditorEVAP = propertyEditorEVAP
        
        self.mandatory = isMandatory
        
        self.args = args    #remember the dictionary of arguments
        
    #---------------------------------------------------------------#

    def getIdentity(self):
        """
            Returns the unique identity for the property, which is 
            used to identify the property in an object
            
            RETURN TYPE: String
        """
        
        return self.identity

    #---------------------------------------------------------------#

    def getDisplayName(self):
        """
            Returns the string name for the property used to display
            and construct property editors
            RETURN TYPE: String
        """
        
        return self.displayName

    #---------------------------------------------------------------#

    def setDisplayName(self, displayName):
        """
            Sets the string name for the property used to display
            and construct property editors
        """
        
        self.displayName = displayName

    #---------------------------------------------------------------#

    def getDescription(self):
        """
            Returns the string description for the property.  Used to 
            display construct property editors
            RETURN TYPE: String
        """
        
        return self.description

    #---------------------------------------------------------------#

    def setDescription(self, description):
        """
            Sets the string description for the property.  Used to 
            display construct property editors
            RETURN TYPE: String
        """
        
        self.description = description

    #---------------------------------------------------------------#

    def getDisplayOrder(self):
        """
            Returns the relative position of the property with in
            the collection of registered properties.  Used to
            construct properties editors
        """
        
        return self.displayOrder

    #---------------------------------------------------------------#

    def setDisplayOrder(self, displayOrder):
        """
            Sets the relative position of the property with in
            the collection of registered properties.  
        """
        
        self.displayOrder = displayOrder

    #---------------------------------------------------------------#

    def getDefaultValue(self):
        """
            Returns the default value for the property.  The default 
            value is used when creating new instances (via
            the property editor)
        """
        
        return self.defaultValue

    #---------------------------------------------------------------#

    def setDefaultValue(self, defaultValue):
        """
            Sets the default value for the property.  The default 
            value is used when creating new instances (via
            the property editor)
        """
        
        self.defaultValue = defaultValue

    #---------------------------------------------------------------#

    def getType(self):
        """
            Returns the property type.  See PropertyTypes.py for a
            list of known types
        """
        
        return self.propertyType

    #---------------------------------------------------------------#

    def getStructure(self):
        """
            Returns the property structure.  See PropertyStructures.py for a
            list of known structures
        """
        
        return self.propertyStructure

    #---------------------------------------------------------------#

    def getFormat(self):
        """
            Returns the property format.  See PropertyFormats.py for a
            list of known formats 
        """
        
        return self.propertyFormat

    #---------------------------------------------------------------#

    def hasEnumeratedValues(self):
        """
            Returns if the property value may only belong to one
            of the enumerated values provided by the
            getEnumeratedValues() method
        """
        
        return self.isEnumeratedProperty

    #---------------------------------------------------------------#

    def getEnumeratedValues(self, context=None):
        """
            ABSTRACT: Returns a list of (DisplayName, Value) tuples for the
            valid enumerated values.  This method is used to
            determine valid values for enumerated property types
            and the display names of the values.
            
            RETURNS: [(DisplayName, Value)]
        """

        if self.hasEnumeratedValues():
            if self.enumeratedValuesFunction != None:
                return self.enumeratedValuesFunction(context)
            elif self.enumeratedValuesMapping != None:
                return self.enumeratedValuesMapping
            elif self.enumeratedValuesList != None:
                return zip(self.enumeratedValuesList, self.enumeratedValuesList)
            else:
                return []
        else:
            return []

    #---------------------------------------------------------------#

    def requiresConstructor(self):
        """
            Returns if the property should be displayed in 
            the constructor for a new object
        """
        
        return self.getConstructorEVAP() != evap_NONE

    #---------------------------------------------------------------#

    def requiresPropertyEditor(self):
        """
            Returns if the property should be displayed in 
            the standard property editor for an existing object
        """
        
        return self.getPropertyEditorEVAP() != evap_NONE

    #---------------------------------------------------------------#

    def getConstructorEVAP(self):
        """
            Return the Editlet Value Access Permission for the property when
            in a constructor property editor
        """
        
        return self.constructorEVAP

    #---------------------------------------------------------------#

    def setConstructorEVAP(self, evap):
        """
            Set the Editlet Value Access Permission for the property when
            in a constructor property editor
        """
        
        self.constructorEVAP = evap

    #---------------------------------------------------------------#

    def getPropertyEditorEVAP(self):
        """
            Return the Editlet Value Access Permission for the property when
            in a standard property editor
        """
        
        return self.propertyEditorEVAP

    #---------------------------------------------------------------#

    def setPropertyEditorEVAP(self, evap):
        """
            Set the Editlet Value Access Permission for the property when
            in a standard property editor
        """
        
        self.propertyEditorEVAP = evap

    #---------------------------------------------------------------#

    def isMandatory(self):
        """
            Returns if a property value must be provided  
            when edited with the property editor 
        """
        
        return self.mandatory
        
    #---------------------------------------------------------------#

    def verifyMandatoryValue(self, value):
        """
            Throws a ValueError if the given value was flagged as mandatory
            and was not given
        """

        if self.isMandatory() and not value:
            raise ValidationException,  ("The property '%s' is required." % self.getDisplayName())

    #---------------------------------------------------------------#

    def hasArg(self, name):
        """
            Return if the specified additional named argument
            has been defined on this property definition
        """
        
        return self.args.has_key(name)
        
    #---------------------------------------------------------------#

    def getArg(self, name, defaultValue=None):
        """
            Return the specified additional named argument
            that has been defined on this property definition.
            
            Returns None if not defined
        """
        
        if self.hasArg(name):
            return self.args[name]
        else:
            return defaultValue
        
    #---------------------------------------------------------------#

    def setArg(self, name, value):
        """
            Sets the specified additional named argument
            on this property definition.
        """
        
        self.args[name] = value
        
    #---------------------------------------------------------------#

    def getEditlet(self, propertyAccess=None, value=None, isForConstructor=false, REQUEST=None, context=None):
        """
            FINAL: Returns an appropriate Editlet that may be used to display
            and edit the valus of the property.  Returns None if
            no Editlet is available and thus the property can not be edited.
            
            PARAMETERS:
            
            propertyAccess:     (Optional) An object implementing the
                                PropertyAccess interface may be provided
                                to enable editlet customisation based
                                on the actual value of the property.
                                Typically a GenericObject instance would
                                be provided as GenericObject implements
                                the PropertyAccess interface.  If an object
                                is not provided, the value for the property 
                                may be specified using the value parameter.

            value:              (Optional) The value for the property
                                that we wish to edit.  As mentioned above,
                                the value may be used to further customize
                                the editlet returned for editing the property.
                            
            isForConstructor    (Optional) Is the Editlet for the an
                                object constructor.  Default is false.

            REQUEST             (Optional) The Request Object from Zope, providing 
                                information about the context in which the 
                                editlet is required.  Default is None.
        """
        
        #use the knowledge engine core to negotiate a suitable Editlet
        return getEditletFor(self, propertyAccess, value, isForConstructor, REQUEST, context)
        
    #---------------------------------------------------------------#


class ValidationException(Exception):
    """
        Thrown when a property value does not validate
    """
    def __init__(self, value, component=None):
        self.value = value
        self.component = component
        
    def getValue(self):
        return self.value        

    def getComponent(self):
        """ Returns the component that triggered the validation error """
        return self.component

    def __str__(self):
        return repr(self.value)

class FieldRequiredException(ValidationException):
    """
        A specific validation error that occurs when a mandatory field is left blank
    """

    pass

class UniqueKeyException(ValidationException):
    """
        A specific validation error that occurs when a unique key has a conflict
    """

    pass


#####################################################################
## Class Initialisation
InitializeClass(PropertyDefinition)
